<?php
//
//	file: sys/io.class.php
//	author: Pierre ROGE
//	begin: 07/07/2007
//	version: 0.0.2 - 24/01/2008
//	license: http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
//

if ( !defined('SYS') )
{
	die('Not allowed');
}

class sys_io extends sys_stdclass
{
	var $magic_quotes_gpc;
	var $string_type;
	var $channels;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->channels = false;
		$this->magic_quotes_gpc = false;
		$this->string_type = false;
	}

	function __destruct()
	{
		unset($this->string_type);
		unset($this->magic_quotes_gpc);
		unset($this->channels);
		parent::__destruct();
	}

	// public: init the instance
	function set()
	{
		$this->magic_quotes_gpc = get_magic_quotes_gpc();
		$this->string_type = gettype(' ');

		// choose between old fashion & new fashion: we expect all the vars to exists except _SESSION, whatever the method
		// nb.: _REQUEST channel is considered being gp and not gpc
		if ( isset($_GET) && isset($_POST) )
		{
			$this->channels = array(
				'_GET' => '_GET',
				'_POST' => '_POST',
				'_COOKIE' => '_COOKIE',
				'_SERVER' => '_SERVER',
				'_ENV' => '_ENV',
				'_FILES' => '_FILES',
			);
			if ( isset($_SESSION) )
			{
				$this->channels['_SESSION'] = '_SESSION';
			}
		}
		else
		{
			$this->channels = array(
				'_GET' => 'HTTP_GET_VARS',
				'_POST' => 'HTTP_POST_VARS',
				'_COOKIE' => 'HTTP_COOKIE_VARS',
				'_SERVER' => 'HTTP_SERVER_VARS',
				'_ENV' => 'HTTP_ENV_VARS',
				'_FILES' => 'HTTP_POST_FILES',
			);
			if ( isset($GLOBALS['HTTP_SESSION_VARS']) )
			{
				$this->channels['_SESSION'] = 'HTTP_SESSION_VARS';
			}
		}

		// we ensure GLOBALS is not used within a channel to overwrite the real $GLOBALS
		// this is the only case we are going to work with $_REQUEST
		if ( (isset($_REQUEST) && is_array($_REQUEST) && isset($_REQUEST['GLOBALS'])) || $this->exists('GLOBALS', array_keys($this->channels)) )
		{
			die('Not allowed');
		}
	}

	// public: check if a button has been pressed (only $_POST is checked)
	// - return:
	//		o true if button activated
	function button($var)
	{
		return $this->exists($var, '_POST') || ($this->exists($var . '_x', '_POST') && $this->exists($var . '_y', '_POST'));
	}

	// public: check the channels list and detect where the var stands
	//		if channel not provided, _REQUEST will be used (GET, POST)
	//		if channel is equal to *ALL, all channels will be scaned
	// - return:
	//		o channel the var is present
	function exists($var, $channels=null)
	{
		if ( ($channels = $this->_scan($channels)) )
		{
			foreach ( $channels as $channel )
			{
				if ( $this->_exists($var, $channel) )
				{
					return $channel;
				}
			}
		}
		return false;
	}

	// public: read a var value within the channels choosen
	//		we don't care the $dft value itself, only its type
	//		if channel not provided, _REQUEST will be used (GET, POST), if channel is equal to *ALL, all channels will be scaned
	// - entry:
	//		o $var: string: var name,
	//		o $dft: 0, '', array(''), array(0), null: default value type
	//		o $channels: string or array of strings: channel list
	//		o $no_norm: boolean: if true, skip the utf-8 normalization for strings
	// - return:
	//		o value of the var within the channel(s), else "empty" type casted on dft type if the var is not found
	//
	function read($var, $dft=null, $channels=false, $no_norm=false)
	{
		// get the value of the var
		$res = false;
		if ( ($channels = $this->_scan($channels)) )
		{
			foreach ( $channels as $channel )
			{
				if ( ($res = $this->_exists($var, $channel)) )
				{
					if ( $channel == '_GET' )
					{
						$res[0] = urldecode($res[0]);
					}
					break;
				}
			}
		}

		// var does not exists, so return "empty" type casted like $dft
		if ( !$res )
		{
			$res = is_null($dft) ? null : (is_array($dft) ? array() : '');
			if ( !is_null($res) && !is_array($res) )
			{
				settype($res, gettype($dft));
			}
			return $res;
		}
		list($value, $channel) = $res;

		// no type, get it from the value itself
		if ( is_null($dft) )
		{
			if ( is_array($value) )
			{
				$dft = array();
				if ( !empty($value) )
				{
					$key = $val = '';
					$type = $this->_get_array_type($value);
					$dft = array($this->_settype($key, $type[0]) => $this->_settype($val, $type[1]));
				}
			}
			else
			{
				$dft = '';
				settype($dft, gettype($value));
			}
		}

		// type cast the value
		if ( is_array($dft) )
		{
			$res = array();
			if ( !empty($dft) && is_array($value) && !empty($value) )
			{
				$type = $this->_get_array_type($dft);
				foreach ( $value as $key => $val )
				{
					$res[ $this->_settype($key, $type[0], $no_norm) ] = $this->_settype($val, $type[1], $no_norm);
				}
			}
		}
		else
		{
			$res = $this->_settype(is_array($value) ? '' : $value, gettype($dft), $no_norm);
		}
		return $res;
	}

	// private: return a full channels list validated
	function _scan($channels)
	{
		$all_channels = array_flip(array('_GET', '_POST', '_COOKIE', '_SERVER', '_ENV', 'getenv', '_FILES', '_SESSION'));
		$channels = !$channels ? array('_REQUEST') : (!is_array($channels) ? array($channels) : $channels);
		$res = array();
		foreach ( $channels as $channel )
		{
			if ( $channel == '*ALL' )
			{
				$res += $all_channels;
			}
			else if ( $channel == '_REQUEST' )
			{
				$res += array_flip(array('_GET', '_POST'));
			}
			else if ( $channel == '_SERVER' )
			{
				$res += array_flip(array('_SERVER', '_ENV', 'getenv'));
			}
			else if ( $channel == '_ENV' )
			{
				$res += array_flip(array('_ENV', 'getenv'));
			}
			else if ( isset($all_channels[$channel]) )
			{
				$res[$channel] = true;
			}
		}
		return empty($res) ? false : array_keys($res);
	}

	// private: return array [$value, $channel] if the channel exists and if the var exists in the channel, else false
	function _exists($var, $channel)
	{
		if ( $channel == 'getenv' )
		{
			$value = getenv($var);
			if ( ($value === false) && function_exists('apache_getenv') )
			{
				$value = apache_getenv($var);
			}
			return ($value === false) ? false : array($this->magic_quotes_gpc ? addslashes($value) : $value, $channel);
		}
		return isset($this->channels[$channel]) && isset($GLOBALS[ $this->channels[$channel] ]) && is_array($GLOBALS[ $this->channels[$channel] ]) && isset($GLOBALS[ $this->channels[$channel] ][$var]) ? array($GLOBALS[ $this->channels[$channel] ][$var], $channel) : false;
	}

	// private: retrieve an array type
	function _get_array_type($var)
	{
		$val = reset($var);
		$key = key($var);

		// we don't accept for now array(array()) as var structure
		return array(gettype($key), is_array($val) ? $this->string_type : gettype($val));
	}

	// private: force the type of a var. $no_norm will be used only for passwords, as they will be processed with md5() or similar
	function _settype($var, $type, $no_norm=false)
	{
		// force type
		settype($var, $type);

		// strings
		if ( ($type == $this->string_type) )
		{
			$var = $this->magic_quotes_gpc ? stripslashes($var) : $var;
			if ( !$no_norm )
			{
				$var = sys::normalize($var);
			}
		}
		return $var;
	}
}

?>